﻿using System;
using System.Linq;
using K9Nano.Configuration.Startup;
using K9Nano.Dependency;
using K9Nano.Runtime;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace K9Nano.MultiTenancy
{
    public class TenantResolverManager : ITenantResolverManager, ISingletonDependency
    {
        private const string AmbientScopeContextKey = "TenantResolverManager";

        private readonly ILogger _logger;
        private readonly IMultiTenancyConfig _multiTenancy;
        private readonly IServiceProvider _serviceProvider;
        private readonly ITenantStore _tenantStore;

        public TenantResolverManager(
            IMultiTenancyConfig multiTenancy,
            IServiceProvider serviceProvider,
            ITenantStore tenantStore,
            ILogger<TenantResolverManager> logger)
        {
            _multiTenancy = multiTenancy;
            _serviceProvider = serviceProvider;
            _tenantStore = tenantStore;
            _logger = logger;
        }

        public Guid? ResolveTenantId()
        {
            if (!_multiTenancy.IsEnabled || !_multiTenancy.Resolvers.Any())
            {
                return null;
            }

            /* TenantResolverManager must be singleton. */
            using var scope = _serviceProvider.CreateScope();

            var ambientScopeProvider = scope.ServiceProvider.GetService<IAmbientScopeProvider<bool>>();

            if (ambientScopeProvider.GetValue(AmbientScopeContextKey))
            {
                //Preventing recursive call of ResolveTenantId
                return null;
            }

            var cache = scope.ServiceProvider.GetService<ITenantResolverCache>();

            using (ambientScopeProvider.BeginScope(AmbientScopeContextKey, true))
            {
                var cacheItem = cache.Value;
                if (cacheItem != null)
                {
                    return cacheItem.TenantId;
                }

                var tenantId = GetTenantIdFromContributors();
                cache.Value = new TenantResolverCacheItem(tenantId);
                return tenantId;
            }
        }

        private Guid? GetTenantIdFromContributors()
        {
            using var scope = _serviceProvider.CreateScope();

            foreach (var resolverType in _multiTenancy.Resolvers)
            {
                var resolver = (ITenantResolver)scope.ServiceProvider.GetService(resolverType);

                Guid? tenantId;

                try
                {
                    tenantId = resolver.ResolveTenantId();
                }
                catch (Exception ex)
                {
                    _logger.LogWarning(ex, $"{resolverType.FullName} resolves failed.");
                    continue;
                }

                if (tenantId == null)
                {
                    continue;
                }

                if (_tenantStore.Find(tenantId.Value) == null)
                {
                    continue;
                }

                return tenantId;

            }

            return null;
        }
    }
}